// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.linqingying.gitcode.pullrequest.ui.selector

import com.intellij.collaboration.ui.CollaborationToolsUIUtil.isDefault
import com.intellij.collaboration.ui.util.bindDisabledIn
import com.intellij.collaboration.ui.util.bindVisibilityIn
import com.intellij.openapi.project.Project
import com.intellij.ui.components.ActionLink
import com.linqingying.gitcode.api.GitCodeServerPath
import com.linqingying.gitcode.authentication.AuthorizationType
import com.linqingying.gitcode.authentication.GCAccountsUtil
import com.linqingying.gitcode.authentication.accounts.GCAccountManager
import com.linqingying.gitcode.authentication.ui.GCAccountsDetailsProvider
import com.linqingying.gitcode.i18n.GitCodeBundle
import com.linqingying.gitcode.ui.util.GCUIUtil
import com.linqingying.gitcode.util.GCGitRepositoryMapping
import git4idea.remote.hosting.ui.RepositoryAndAccountSelectorComponentFactory
import kotlinx.coroutines.CoroutineScope
import java.awt.event.ActionEvent
import javax.swing.AbstractAction
import javax.swing.JButton
import javax.swing.JComponent

class GCRepositoryAndAccountSelectorComponentFactory internal constructor(
    private val project: Project,
    private val vm: GCRepositoryAndAccountSelectorViewModel,
    private val accountManager: GCAccountManager
) {

    fun create(scope: CoroutineScope): JComponent {
        val accountDetailsProvider = GCAccountsDetailsProvider(scope, accountManager)
        val errorPresenter = GCSelectorErrorStatusPresenter(project) {
            vm.submitSelection()
        }

        return RepositoryAndAccountSelectorComponentFactory(vm)
            .create(
                scope = scope,
                repoNamer = { mapping ->
                    val allRepositories = vm.repositoriesState.value.map { it.repository }
                    GCUIUtil.getRepositoryDisplayName(allRepositories, mapping.repository, true)
                },
                detailsProvider = accountDetailsProvider,
                accountsPopupActionsSupplier = { createPopupLoginActions(it) },
                submitActionText = GitCodeBundle.message("pull.request.view.list"),
                loginButtons = createLoginButtons(scope),
                errorPresenter = errorPresenter
            )
    }

    private fun createLoginButtons(scope: CoroutineScope): List<JButton> {
        return listOf(
            JButton(GitCodeBundle.message("action.gitcode.Accounts.AddGCAccount.text")).apply {
                isDefault = true
                isOpaque = false

                addActionListener {
                    if (loginToGitCode(false, AuthorizationType.OAUTH)) {
                        vm.submitSelection()
                    }
                }

                bindVisibilityIn(scope, vm.gitcodeLoginAvailableState)
                bindDisabledIn(scope, vm.busyState)
            },

            ActionLink(GitCodeBundle.message("action.gitcode.Accounts.AddGCAccountWithToken.text")) {
                if (loginToGitCode(false, AuthorizationType.TOKEN)) {
                    vm.submitSelection()
                }
            }.apply {

                autoHideOnDisable = false
                bindVisibilityIn(scope, vm.gitcodeLoginAvailableState)
                bindDisabledIn(scope, vm.busyState)
            },
            JButton(GitCodeBundle.message("action.gitcode.Accounts.AddGCEAccount.text")).apply {
                isDefault = true
                isOpaque = false

                addActionListener {
                    val repo = vm.repoSelectionState.value ?: return@addActionListener
                    if (loginToGhe(false, repo)) {
                        vm.submitSelection()
                    }
                }

                bindVisibilityIn(scope, vm.gheLoginAvailableState)
                bindDisabledIn(scope, vm.busyState)
            }
        )
    }

    private fun createPopupLoginActions(repo: GCGitRepositoryMapping?): List<AbstractAction> {
        val isDotComServer = repo?.repository?.serverPath?.isGitCodeDotCom ?: false
        return if (isDotComServer)
            listOf(object : AbstractAction(GitCodeBundle.message("action.gitcode.Accounts.AddGCAccount.text")) {
                override fun actionPerformed(e: ActionEvent?) {
                    loginToGitCode(true, AuthorizationType.OAUTH)
                }
            }, object : AbstractAction(GitCodeBundle.message("action.gitcode.Accounts.AddGCAccountWithToken.text")) {
                override fun actionPerformed(e: ActionEvent?) {
                    loginToGitCode(true, AuthorizationType.TOKEN)
                }
            })
        else listOf(
            object : AbstractAction(GitCodeBundle.message("action.gitcode.Accounts.AddGCEAccount.text")) {
                override fun actionPerformed(e: ActionEvent?) {
                    loginToGhe(true, repo!!)
                }
            })
    }

    private fun loginToGitCode(forceNew: Boolean, authType: AuthorizationType): Boolean {
        val account = vm.accountSelectionState.value
        if (account == null || forceNew) {
            return GCAccountsUtil.requestNewAccount(
                GitCodeServerPath.DEFAULT_SERVER,
                null,
                project,
                authType = authType
            )?.account?.also {
                vm.accountSelectionState.value = it
            } != null
        } else if (vm.missingCredentialsState.value == true) {
            return GCAccountsUtil.requestReLogin(account, project, authType = authType) != null
        }
        return false
    }

    private fun loginToGhe(forceNew: Boolean, repo: GCGitRepositoryMapping): Boolean {
        val server = repo.repository.serverPath
        val account = vm.accountSelectionState.value
        if (account == null || forceNew) {
            return GCAccountsUtil.requestNewAccount(server, login = null, project = project)?.also {
                vm.accountSelectionState.value = it.account
            } != null
        } else if (vm.missingCredentialsState.value == true) {
            return GCAccountsUtil.requestReLogin(account, project, authType = AuthorizationType.TOKEN) != null
        }
        return false
    }
}

